---
id: rpcauthorization
title: Rpc鉴权授权策略
---

import BilibiliCard from '@site/src/components/BilibiliCard.js';

## 一、说明

鉴权授权策略，用于控制RPC请求的访问权限。是现代api-server的系统最重要的功能之一。

一般的鉴权授权策略可以分为2个大类：

1. 基于会话
2. 基于Token

而基于Token的鉴权授权策略又分为2大类：

1. 基于随机字符串的Token
2. 基于结构化的Token（例如：JWT）

<BilibiliCard title="Rpc鉴权授权说明" link="https://www.bilibili.com/cheese/play/ep1779905" isPro="true"/>

## 二、基于会话的鉴权授权策略

基于会话的鉴权授权策略，一般是只应用于面向连接的通信。一般的这种通信具有持久化的特性，并且是有状态的。

<BilibiliCard title="Rpc基于会话完成鉴权授权" link="https://www.bilibili.com/cheese/play/ep1779908" isPro="true"/>

## 三、基于Token的鉴权授权策略

基于Token的鉴权授权策略，一般用于面向消息的通信。

### 3.1 基于随机字符串的Token

<BilibiliCard title="Rpc基于随机字符串完成鉴权授权" link="https://www.bilibili.com/cheese/play/ep1779913" isPro="true"/>

### 3.2 基于结构化的Token（例如：JWT）

<BilibiliCard title="Rpc基于jwt完成鉴权授权" link="https://www.bilibili.com/cheese/play/ep1779922" isPro="true"/>

## 四、鉴权授权策略的比较

好的，我们来详细比较一下随机字符串（通常指无结构会话令牌）和 JWT（JSON Web Token）的优缺点以及适用场景。

**核心区别：**

*   **随机字符串 (无结构令牌)：** 本质上就是一个不可预测的长字符串（如 `f8a3d7c0b1e9`）。它本身**不包含任何有意义的信息**，只是一个指向服务器端存储（通常是数据库或缓存）中会话数据的**引用**。
*   **JWT (结构化令牌)：** 是一个经过数字签名或加密的、**自包含**的结构化字符串。它由三部分组成（Header.Payload.Signature），其中 `Payload` 部分包含了**声明**（通常是关于用户身份和权限的 JSON 对象）。服务器可以验证其签名/加密，并直接从中提取信息，无需查询后端存储（至少在有效期内）。

---

### 4.1 随机字符串 (无结构会话令牌)

*   **优点：**
    1.  **简单轻量：** 生成和验证极其简单快速（生成随机数 -> 存储；收到请求 -> 查存储）。
    2.  **完全控制：** 服务器拥有绝对控制权。可以即时撤销（删除存储条目）、修改关联数据（更新存储条目）或强制过期。
    3.  **无信息泄露：** 令牌本身不包含任何用户或会话信息，即使被截获，攻击者也无法从中直接获取有效内容（前提是字符串本身足够随机）。
    4.  **存储开销可控：** 服务器端存储的大小和内容完全由应用决定。
    5.  **天然防篡改：** 篡改令牌会导致在服务器端存储中查找失败，验证直接不通过。

*   **缺点：**
    1.  **状态性：** 服务器**必须**维护一个存储（数据库、Redis 等）来关联令牌和实际的会话数据。这引入了状态。
    2.  **数据库/缓存依赖：** 每次验证令牌都需要查询后端存储。**在高并发场景下，这可能成为性能瓶颈和单点故障。**
    3.  **扩展性挑战：** 在分布式系统或微服务架构中，需要共享会话存储（例如集中式 Redis 集群）或实现粘性会话（Session Affinity），增加了复杂性和潜在瓶颈。
    4.  **无内置信息：** 令牌本身不携带任何信息，所有数据都需要从存储中获取。

*   **典型使用场景：**
    1.  **传统的 Web 应用会话管理：** 这是最经典的场景。用户登录后，服务器创建会话数据存储在服务端（内存、数据库、Redis），生成一个随机的 Session ID（通常存储在 Cookie 中）返回给浏览器。后续请求携带此 ID，服务器查询存储获取会话状态。
    2.  **一次性令牌：** 如密码重置令牌、邮箱验证令牌。它们通常是随机字符串，存储在数据库并关联特定操作和过期时间，使用一次即失效。
    3.  **简单的 API 认证（较少见）：** 如果 API 调用频率不高或架构简单，也可以使用类似机制（API Key + Secret，但 Secret 通常也需要存储和验证）。
    4.  **需要即时撤销能力的场景：** 如用户登出、管理员踢人下线等，需要立即使令牌失效的场景。

---

### 4.2 JWT (JSON Web Token)

*   **优点：**
    1.  **无状态：** 这是 JWT 最大的优势。服务器不需要在本地或共享存储中保存会话状态。验证仅依赖于签名（和可选的加密）以及预定义的密钥/证书。**极大简化了服务器架构，消除了数据库查询瓶颈。**
    2.  **自包含：** Payload 中可以携带有用的声明（用户名、用户ID、角色、权限、过期时间等）。验证通过后，服务器可以直接使用这些信息，无需额外查询。
    3.  **扩展性极佳：** 完美适应分布式系统和微服务架构。任何服务实例只要拥有验证签名/解密的密钥，都可以独立验证令牌并提取所需信息。**天然支持跨域/跨服务认证。**
    4.  **性能（特定场景）：** 避免了每次请求的存储查询开销（验证签名的计算开销通常远小于网络 I/O 和数据库查询）。在分布式系统中，优势尤其明显。
    5.  **标准化：** 是 IETF 标准 (RFC 7519)，有成熟的库支持多种语言，互操作性好。

*   **缺点：**
    1.  **体积较大：** 由于是 Base64 编码的 JSON，通常比随机字符串长很多。在带宽敏感或需频繁传输的场景（如放在 HTTP Header 中）可能成为负担。
    2.  **难以主动撤销/失效：** 一旦签发，在自然过期前，很难强制使其失效（因为服务端无状态）。实现主动撤销需要额外机制（如黑名单、短期有效期 + Refresh Token），增加了复杂性。
    3.  **安全风险：**
        *   **令牌泄露：** 如果 JWT 被盗（如 XSS、中间人攻击），攻击者可以在有效期内冒充用户（“持票攻击”）。缩短有效期和使用 HTTPS 是必须的。
        *   **签名算法安全：** 如果使用弱算法（如 `HS256` 密钥太弱）或实现不当（如未验证签名），可能被伪造。必须使用强算法（如 `RS256`，`ES256`）并妥善保管私钥/密钥。
        *   **敏感数据泄露：** Payload 默认是 Base64 编码（可逆），**不应在其中存放密码、信用卡号等绝对敏感信息**。如需保密，应使用 JWE（JSON Web Encryption）进行加密。
    4.  **客户端存储责任：** 令牌由客户端（浏览器、移动 App）存储和管理，需要防范 XSS、CSRF 等攻击。
    5.  **实现复杂度：** 需要正确理解和实现签名/验证、密钥管理、声明处理、过期处理等，比生成随机字符串更复杂。

*   **典型使用场景：**
    1.  **跨域/单点登录：** 用户在一个域登录后，可以获取一个 JWT，用于访问其他信任该 JWT 签发方的应用或服务。OAuth 2.0 / OpenID Connect 的核心令牌之一就是 JWT。
    2.  **API 认证与授权：** 现代 RESTful / GraphQL API 的**首选认证机制**。客户端在登录后获取 JWT，后续请求在 `Authorization: Bearer <token>` 头中携带。API 服务器验证签名和声明即可判断身份和权限。
    3.  **无状态微服务间通信：** 微服务 A 可以将包含用户上下文的 JWT 传递给微服务 B，B 能独立验证并使用其中的信息，无需调用中心化的认证服务或共享会话存储。
    4.  **信息交换：** 作为在双方之间安全传递声明信息的一种方式（需签名/加密确保完整性和保密性），例如在 OAuth 流程中传递用户信息（ID Token）。

---

### 4.3 总结与选择建议

| 特性         | 随机字符串 (无结构令牌)                           | JWT (JSON Web Token)                                |
| :----------- | :------------------------------------------------ | :-------------------------------------------------- |
| **核心**     | **引用** (指向服务器存储的数据)                   | **自包含** (数据在令牌内)                           |
| **状态**     | **有状态** (服务器需存储会话)                     | **无状态** (服务器无需存储会话)                     |
| **信息携带** | 无                                                | 有 (Payload 中的声明)                               |
| **验证方式** | 查数据库/缓存                                     | 验证签名/解密                                       |
| **性能**     | 每次请求需查存储 (可能成瓶颈)                     | 无存储查询 (签名验证计算开销小)                     |
| **扩展性**   | 差 (需共享存储或粘性会话)                         | **极佳** (天然支持分布式)                           |
| **撤销**     | **容易** (直接删除存储条目)                       | **困难** (需额外机制如黑名单、短有效期+Refresh)     |
| **大小**     | 小                                                | 较大                                               |
| **安全性**   | 依赖存储安全、传输安全；令牌本身无信息            | 依赖算法强度、密钥管理、传输安全；需防泄露、篡改    |
| **复杂度**   | 低 (生成随机数、存、查)                           | 中高 (签名/验证、密钥管理、声明处理、安全最佳实践) |
| **典型场景** | **传统Web会话** <br/> **一次性令牌** <br/> 需即时撤销 | **API 认证**<br/>**单点登录 / SSO**<br/>**微服务通信** |

**如何选择？**

1.  **选择随机字符串 (无结构令牌) 当：**
    *   你正在构建一个传统的、服务器端渲染的 Web 应用。
    *   你需要**立即且可靠地撤销令牌**的能力（如用户登出）。
    *   应用是单体或规模较小，维护共享会话存储不是问题。
    *   令牌本身不需要携带额外的声明信息。
    *   对性能要求极高且请求非常密集，且能承受存储查询开销（或有强大缓存）。

2.  **选择 JWT 当：**
    *   你在构建 **API**（尤其是 RESTful / GraphQL）。
    *   你需要 **SSO** 或 **跨域/跨服务认证**。
    *   你的架构是 **分布式、微服务**，需要服务间传递用户上下文。
    *   **可扩展性** 和 **消除数据库依赖** 是主要目标。
    *   令牌需要携带一些**非绝对敏感**的声明（用户ID、角色、权限），并让服务端能直接使用。
    *   你愿意并能够处理 JWT 的安全复杂性（签名、密钥管理、短有效期、HTTPS）和潜在的撤销难题。

**重要安全提示：**

*   **无论使用哪种方式，都必须通过 HTTPS 传输令牌！**
*   **JWT 的 Payload 不是加密的（除非使用 JWE），不要存敏感数据！**
*   **使用强随机数生成器创建随机字符串或 JWT 的 `jti` (JWT ID)。**
*   **仔细选择并安全配置 JWT 的签名算法和密钥。**
*   **始终验证 JWT 的签名、有效期 (`exp`)、受众 (`aud`) 等关键声明。**
*   **考虑令牌泄露的风险，使用较短的过期时间，并为 JWT 实现合理的撤销机制（如黑名单）或结合使用 Refresh Token。**

在实践中，两者也并非完全互斥。例如，一个系统可能使用基于随机字符串的传统会话管理 Web 界面，而其后台 API 则使用 JWT 进行交互。或者，在 JWT 的流程中，Refresh Token 本身可能是一个存储在数据库中的随机字符串。理解各自的优缺点有助于你为系统的不同部分选择最合适的工具。

## 五、客户端传递Token

### 5.1 客户端使用HttpClient请求授权

<BilibiliCard title="客户端使用HttpClient请求授权" link="https://www.bilibili.com/cheese/play/ep1779934" isPro="true"/>

### 5.2 客户端使用WebApiClient请求授权

<BilibiliCard title="客户端使用WebApiClient请求授权" link="https://www.bilibili.com/cheese/play/ep1779935" isPro="true"/>

### 5.3 客户端使用DmtpRpc请求授权

<BilibiliCard title="客户端使用DmtpRpc请求授权" link="https://www.bilibili.com/cheese/play/ep1779937" isPro="true"/>

### 5.4 客户端使用通用Rpc请求授权

<BilibiliCard title="通用Rpc请求授权" link="https://www.bilibili.com/cheese/play/ep1779955" isPro="true"/>

### 5.5 基于角色的授权

<BilibiliCard title="基于角色的授权" link="https://www.bilibili.com/cheese/play/ep1779956" isPro="true"/>
